/**
 * @author Simon Richardson - simon@ustwo.co.uk
 */
package org.osflash.signals;

import asunit.asserts.AssertEquals;
import asunit.asserts.AssertFalse;
import asunit.asserts.AssertNotNull;
import asunit.asserts.AssertNull;
import asunit.asserts.AssertTrue;
import asunit.asserts.Fail;
import asunit.framework.IAsync;
import org.osflash.signals.events.GenericEvent;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;

class ISlotTestBase {

	@:meta(Inject())
	public var async : IAsync;
	public var signal : ISignal;
	@:meta(After())
	public function tearDown() : Void {
		signal.removeAll();
		signal = null;
	}

	function checkGenericEvent(e : GenericEvent) : Void {
		assertNull("event.signal is not set by Signal", e.signal);
		assertNull("event.target is not set by Signal", e.target);
	}

	@:meta(Test())
	public function add_listener_pause_on_slot_should_not_dispatch() : Void {
		var slot : ISlot = signal.add(failIfCalled);
		slot.enabled = false;
		signal.dispatch(new Event("click"));
	}

	@:meta(Test())
	public function add_listener_switch_pause_and_resume_on_slot_should_not_dispatch() : Void {
		var slot : ISlot = signal.add(failIfCalled);
		slot.enabled = false;
		slot.enabled = true;
		slot.enabled = false;
		signal.dispatch(new Event("click"));
	}

	@:meta(Test())
	public function add_listener_then_dispatch_change_listener_on_slot_should_dispatch_second_listener() : Void {
		var slot : ISlot = signal.add(newEmptyHandler());
		signal.dispatch(new Event("click"));
		slot.listener = newEmptyHandler();
		signal.dispatch(new Event("click"));
	}

	@:meta(Test())
	public function add_listener_then_dispatch_change_listener_on_slot_then_pause_should_not_dispatch_second_listener() : Void {
		var slot : ISlot = signal.add(newEmptyHandler());
		signal.dispatch(new Event("click"));
		slot.listener = failIfCalled;
		slot.enabled = false;
		signal.dispatch(new Event("click"));
	}

	@:meta(Test())
	public function add_listener_then_change_listener_then_switch_back_and_then_should_dispatch() : Void {
		var slot : ISlot = signal.add(newEmptyHandler());
		signal.dispatch(new Event("click"));
		var listener : Dynamic = slot.listener;
		slot.listener = failIfCalled;
		slot.listener = listener;
		signal.dispatch(new Event("click"));
	}

	@:meta(Test())
	public function addOnce_listener_pause_on_slot_should_not_dispatch() : Void {
		var slot : ISlot = signal.addOnce(failIfCalled);
		slot.enabled = false;
		signal.dispatch(new Event("click"));
	}

	@:meta(Test())
	public function addOnce_listener_switch_pause_and_resume_on_slot_should_not_dispatch() : Void {
		var slot : ISlot = signal.addOnce(failIfCalled);
		slot.enabled = false;
		slot.enabled = true;
		slot.enabled = false;
		signal.dispatch(new Event("click"));
	}

	@:meta(Test())
	public function addOnce_listener_then_dispatch_change_listener_on_slot_should_dispatch_second_listener() : Void {
		var slot : ISlot = signal.addOnce(newEmptyHandler());
		signal.dispatch(new Event("click"));
		slot.listener = newEmptyHandler();
		signal.dispatch(new Event("click"));
	}

	@:meta(Test())
	public function addOnce_listener_then_dispatch_change_listener_on_slot_then_pause_should_not_dispatch_second_listener() : Void {
		var slot : ISlot = signal.addOnce(newEmptyHandler());
		signal.dispatch(new Event("click"));
		slot.listener = failIfCalled;
		slot.enabled = false;
		signal.dispatch(new Event("click"));
	}

	@:meta(Test())
	public function addOnce_listener_then_change_listener_then_switch_back_and_then_should_dispatch() : Void {
		var slot : ISlot = signal.addOnce(newEmptyHandler());
		signal.dispatch(new Event("click"));
		var listener : Dynamic = slot.listener;
		slot.listener = failIfCalled;
		slot.listener = listener;
		signal.dispatch(new Event("click"));
	}

	@:meta(Test())
	public function add_listener_and_verify_once_is_false() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot : ISlot = signal.add(listener);
		assertFalse("Slot once is false", slot.once);
	}

	@:meta(Test())
	public function add_listener_and_verify_priority_is_zero() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot : ISlot = signal.add(listener);
		assertTrue("Slot priority is zero", slot.priority == 0);
	}

	@:meta(Test())
	public function add_listener_and_verify_slot_listener_is_same() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot : ISlot = signal.add(listener);
		assertTrue("Slot listener is the same as the listener", slot.listener == listener);
	}

	@:meta(Test())
	public function add_same_listener_twice_and_verify_slots_are_the_same() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot0 : ISlot = signal.add(listener);
		var slot1 : ISlot = signal.add(listener);
		assertTrue("Slots are equal if they're they have the same listener", slot0 == slot1);
	}

	@:meta(Test())
	public function add_same_listener_twice_and_verify_slot_listeners_are_the_same() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot0 : ISlot = signal.add(listener);
		var slot1 : ISlot = signal.add(listener);
		assertTrue("Slot listener is the same as the listener", slot0.listener == slot1.listener);
	}

	@:meta(Test())
	public function add_listener_and_remove_using_slot() : Void {
		var slot : ISlot = signal.add(newEmptyHandler());
		slot.remove();
		assertTrue("Number of listeners should be 0", signal.numListeners == 0);
	}

	@:meta(Test())
	public function add_same_listener_twice_and_remove_using_slot_should_have_no_listeners() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot0 : ISlot = signal.add(listener);
		signal.add(listener);
		slot0.remove();
		assertTrue("Number of listeners should be 0", signal.numListeners == 0);
	}

	@:meta(Test())
	public function add_lots_of_same_listener_and_remove_using_slot_should_have_no_listeners() : Void {
		var listener : Dynamic = newEmptyHandler();
		var i : Int = 0;
		while(i < 100) {
			var slot0 : ISlot = signal.add(listener);
			i++;
		}
		slot0.remove();
		assertTrue("Number of listeners should be 0", signal.numListeners == 0);
	}

	@:meta(Test(expects="ArgumentError"))
	public function add_listener_then_set_listener_to_null_should_throw_ArgumentError() : Void {
		var slot : ISlot = signal.add(newEmptyHandler());
		slot.listener = null;
	}

	@:meta(Test())
	public function add_listener_and_call_execute_on_slot_should_call_listener() : Void {
		var slot : ISlot = signal.add(newEmptyHandler());
		slot.execute([1, 2, 3, 4, 5, 6, 7, 8, 9]);
	}

	@:meta(Test())
	public function add_listener_twice_and_call_execute_on_slot_should_call_listener_and_not_on_signal_listeners() : Void {
		signal.add(failIfCalled);
		var slot : ISlot = signal.add(newEmptyHandler());
		slot.execute([1, 2, 3, 4, 5, 6, 7, 8, 9]);
	}

	@:meta(Test())
	public function addOnce_listener_and_verify_once_is_true() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot : ISlot = signal.addOnce(listener);
		assertTrue("Slot once is true", slot.once == true);
	}

	@:meta(Test())
	public function addOnce_listener_and_verify_priority_is_zero() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot : ISlot = signal.addOnce(listener);
		assertTrue("Slot priority is zero", slot.priority == 0);
	}

	@:meta(Test())
	public function addOnce_listener_and_verify_slot_listener_is_same() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot : ISlot = signal.addOnce(listener);
		assertTrue("Slot listener is the same as the listener", slot.listener == listener);
	}

	@:meta(Test())
	public function addOnce_same_listener_twice_and_verify_slots_are_the_same() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot0 : ISlot = signal.addOnce(listener);
		var slot1 : ISlot = signal.addOnce(listener);
		assertTrue("Slots are equal if they're they have the same listener", slot0 == slot1);
	}

	@:meta(Test())
	public function addOnce_same_listener_twice_and_verify_slot_listeners_are_the_same() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot0 : ISlot = signal.addOnce(listener);
		var slot1 : ISlot = signal.addOnce(listener);
		assertTrue("Slot listener is the same as the listener", slot0.listener == slot1.listener);
	}

	@:meta(Test())
	public function addOnce_listener_and_remove_using_slot() : Void {
		var slot : ISlot = signal.addOnce(newEmptyHandler());
		slot.remove();
		assertTrue("Number of listeners should be 0", signal.numListeners == 0);
	}

	@:meta(Test())
	public function addOnce_same_listener_twice_and_remove_using_slot_should_have_no_listeners() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot0 : ISlot = signal.addOnce(listener);
		signal.addOnce(listener);
		slot0.remove();
		assertTrue("Number of listeners should be 0", signal.numListeners == 0);
	}

	@:meta(Test())
	public function addOnce_lots_of_same_listener_and_remove_using_slot_should_have_no_listeners() : Void {
		var listener : Dynamic = newEmptyHandler();
		var i : Int = 0;
		while(i < 100) {
			var slot0 : ISlot = signal.addOnce(listener);
			i++;
		}
		slot0.remove();
		assertTrue("Number of listeners should be 0", signal.numListeners == 0);
	}

	@:meta(Test(expects="ArgumentError"))
	public function addOnce_listener_then_set_listener_to_null_should_throw_ArgumentError() : Void {
		var slot : ISlot = signal.addOnce(newEmptyHandler());
		slot.listener = null;
	}

	@:meta(Test())
	public function addOnce_listener_and_call_execute_on_slot_should_call_listener() : Void {
		var slot : ISlot = signal.addOnce(newEmptyHandler());
		slot.execute([1, 2, 3, 4, 5, 6, 7, 8, 9]);
	}

	@:meta(Test())
	public function addOnce_listener_twice_and_call_execute_on_slot_should_call_listener_and_not_on_signal_listeners() : Void {
		signal.addOnce(failIfCalled);
		var slot : ISlot = signal.addOnce(newEmptyHandler());
		slot.execute([1, 2, 3, 4, 5, 6, 7, 8, 9]);
	}

	@:meta(Test())
	public function slot_params_are_null_when_created() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot : ISlot = signal.add(listener);
		assertNull("params should be null", slot.params);
	}

	@:meta(Test())
	public function slot_params_should_not_be_null_after_adding_array() : Void {
		var listener : Dynamic = newEmptyHandler();
		var slot : ISlot = signal.add(listener);
		slot.params = [];
		assertNotNull("params should not be null", slot.params);
	}

	@:meta(Test())
	public function slot_params_with_one_param_should_be_sent_through_to_listener() : Void {
		var listener : Dynamic = function(e : Event) : Void {
			assertNotNull(e);
			assertTrue(Std.is(args[0], Int));
			assertEquals(args[0], 1234);
		}
;
		var slot : ISlot = signal.add(listener);
		slot.params = [1234];
		signal.dispatch(new MouseEvent("click"));
	}

	@:meta(Test())
	public function slot_params_with_multiple_params_should_be_sent_through_to_listener() : Void {
		var listener : Dynamic = function(e : Event) : Void {
			assertNotNull(e);
			assertTrue(Std.is(args[0], Int));
			assertEquals(args[0], 12345);
			assertTrue(Std.is(args[1], String));
			assertEquals(args[1], "text");
			assertTrue(Std.is(args[2], Sprite));
			assertEquals(args[2], slot.params[2]);
		}
;
		var slot : ISlot = signal.add(listener);
		slot.params = [12345, "text", new Sprite()];
		signal.dispatch(new MouseEvent("click"));
	}

	@:meta(Test())
	public function slot_params_should_not_effect_other_slots() : Void {
		var listener0 : Dynamic = function(e : Event) : Void {
			assertNotNull(e);
			assertEquals(arguments.length, 1);
		}
;
		signal.add(listener0);
		var listener1 : Dynamic = function(e : Event) : Void {
			assertNotNull(e);
			assertEquals(arguments.length, 2);
			assertEquals(arguments[1], 123456);
		}
;
		var slot : ISlot = signal.add(listener1);
		slot.params = [123456];
		signal.dispatch(new MouseEvent("click"));
	}

	@:meta(Test())
	public function verify_chaining_of_slot_params() : Void {
		var listener : Dynamic = function(e : Event) : Void {
			assertNotNull(e);
			assertEquals(args.length, 1);
			assertEquals(args[0], 1234567);
		}
;
		signal.add(listener).params = [1234567];
		signal.dispatch(new MouseEvent("click"));
	}

	@:meta(Test())
	public function verify_chaining_and_concat_of_slot_params() : Void {
		var listener : Dynamic = function(e : Event) : Void {
			assertNotNull(e);
			assertEquals(args.length, 2);
			assertEquals(args[0], 12345678);
			assertEquals(args[1], "text");
		}
;
		signal.add(listener).params = [12345678].concat(["text"]);
		signal.dispatch(new MouseEvent("click"));
	}

	@:meta(Test())
	public function verify_chaining_and_pushing_on_to_slot_params() : Void {
		var listener : Dynamic = function(e : Event) : Void {
			assertNotNull(e);
			assertEquals(args.length, 2);
			assertEquals(args[0], 123456789);
			assertEquals(args[1], "text");
		}
;
		// This is ugly, but I put money on somebody will attempt to do this!
		var slots : ISlot;
		(slots = signal.add(listener)).params = [123456789];
		slots.params.push("text");
		signal.dispatch(new MouseEvent("click"));
	}

	////// UTILITY METHODS //////
	static function newEmptyHandler() : Dynamic {
		return function(e : Dynamic = null) : Void {
		}
;
	}

	static function failIfCalled(e : Dynamic = null) : Void {
		fail("This function should not have been called.");
	}

}

